package sentinel

import (
	"fmt"
	"gc-rds-ha/internal/common"
	"gc-rds-ha/internal/dao/mysql_sync"
	"gc-rds-ha/internal/dao/sentinel"
	msg "gc-rds-ha/scripts"
	"sync"
	"time"

	log "github.com/sirupsen/logrus"
)

const (
	ShowSlaveStatus = "show slave status"
	ResetMaster     = "reset master"
	StartSlave      = "start slave"
)

var (
	c       = int(common.AgentConfig.Sentinel.Concurrency)
	timeout = time.Duration(common.AgentConfig.Sentinel.Timeout) * time.Second
	wg      sync.WaitGroup
)

type RdsClusterSentinel struct {
}

func (s *RdsClusterSentinel) Forget(host string, port uint32) (*mysql_sync.DiscoverFormat, error) {
	panic("implement me")
}

type Recover struct {
	Host         string
	Port         uint32
	Role         string
	ClusterAlias string
	MasterHost   string
	MasterPort   uint32
}

func (s *RdsClusterSentinel) Recover() {
	t := &mysql_sync.MySQLClusterTopology{
		IsLastCheckValid: msg.EIsLastCheckValid_Invalid,
	}
	failOverList, err := t.List(common.Offset, common.Limit)
	if !common.IsOnline() {
		log.WithFields(log.Fields{
			"s":            s,
			"failOverList": failOverList,
		}).Info("Recover.info")
	}
	if err != nil {
		return
	}
	for _, failOverInfo := range failOverList {
		r := &Recover{
			Port:         failOverInfo.Port,
			Host:         failOverInfo.Host,
			ClusterAlias: failOverInfo.ClusterAlias,
		}
		//主库故障
		if failOverInfo.Role == common.MasterName {
			r.doMasterRecover()
		}
		//从库故障
		if failOverInfo.Role == common.SlaveName {
			go r.doSlaveRecover()
		}
	}
}

func (s *RdsClusterSentinel) Discover(host string, port uint32) (*mysql_sync.DiscoverFormat, error) {
	panic("implement me")
}

func (s *RdsClusterSentinel) ClusterAliasInfo(clusterAlias string) ([]mysql_sync.TopologyInfo, error) {
	panic("implement me")
}

func Discover() error {
	//
	proxy := &sentinel.MysqlProxyVip{}
	proxyList, err := proxy.List(common.Offset, common.Limit)
	if err != nil {
		return err
	}
	for i := 0; i < c; i++ {
		go func(i int) {
			defer wg.Done()
			for idx := i; idx < len(proxyList); idx += c {
				Monitor(&proxyList[idx])
			}
		}(i)
	}
	wg.Wait()
	return nil
}

type MonitorResult struct {
	ProxyCh chan bool
	RdsCh   chan bool
}

func Monitor(proxyInfo *sentinel.MysqlProxyVip) {
	mr := MonitorResult{
		ProxyCh: make(chan bool),
		RdsCh:   make(chan bool),
	}
	// 心跳检测-proxy
	go func() {
		mr.ProxyCh <- true
	}()
	select {
	case <-mr.ProxyCh:
		fmt.Println("proxyCh ok .... ")
	case <-time.After(timeout):
		fmt.Println("proxyCh timeout .... ")
	}

	// 心跳检测-rds-mysql
	go func() {
		mr.RdsCh <- true
	}()
	select {
	case <-mr.RdsCh:
		fmt.Println("rdsCh ok .... ")
	case <-time.After(timeout):
		fmt.Println("rdsCh timeout .... ")
	}
}

func FailOver() {

}

func (r *Recover) doSlaveRecover() {
	slaveList, err := r.getFailCluster()
	log.WithFields(log.Fields{
		"r":          r,
		"slaveList:": slaveList,
	}).Info("doSlaveRecover...")
	if err != nil {
		return
	}
	if len(slaveList) > 0 {
		mysqlParams := &MysqlParams{
			User: common.AgentConfig.Sentinel.RdsMysqlUser,
			Pwd:  common.AgentConfig.Sentinel.RdsMysqlPwd,
			Host: slaveList[0].MasterHost,
			Port: slaveList[0].Port,
		}
		db, err := mysqlParams.OpenMysql()
		if err != nil {
			return
		}
		if err = db.Exec("stop slave").Error; err != nil {
			log.WithFields(log.Fields{
				"mysqlParams": mysqlParams,
			}).Error("doSlaveRecover.stop slave error")
			return
		}
		if err = db.Exec(StartSlave).Error; err != nil {
			log.WithFields(log.Fields{
				"mysqlParams": mysqlParams,
			}).Error("doSlaveRecover.start slave error")
			return
		}
	}
}

type ShowSlaveStat struct {
	MasterLogFile      string `gorm:"column:Master_Log_File"`
	ReadMasterLogPos   string `gorm:"column:Read_Master_Log_Pos"`
	RelayMasterLogFile string `gorm:"column:Relay_Master_Log_File"`
	ExecMasterLogPos   string `gorm:"column:Exec_Master_Log_Pos"`
	SlaveIORunning     string `gorm:"column:Slave_IO_Running"`
	SlaveSQLRunning    string `gorm:"column:Slave_SQL_Running"`
}

func (r *Recover) doMasterRecover() {
	yes := "Yes"
	masterList, err := r.getFailCluster()
	log.WithFields(log.Fields{
		"r":           r,
		"masterList:": masterList,
	}).Info("doMasterRecover...")
	if err != nil || len(masterList) < 1 {
		return
	}
	slaveResult := sync.Map{}
	//for {
	for i, info := range masterList {
		wg.Add(1)
		go func(i int, info Recover) {
			defer wg.Done()
			// 查看从节点状态，选出主节点
			mysqlParams := &MysqlParams{
				User: common.AgentConfig.Sentinel.RdsMysqlUser,
				Pwd:  common.AgentConfig.Sentinel.RdsMysqlPwd,
				Host: info.Host,
				Port: info.Port,
			}
			db, err := mysqlParams.OpenMysql()
			if err != nil {
				return
			}
			stat := &ShowSlaveStat{}
			if err = db.Raw(ShowSlaveStatus).Scan(stat).Error; err != nil {
				log.WithFields(log.Fields{
					"info": info,
				}).Error("error")
				return
			}
			mysqlParams.MasterLogFile = stat.MasterLogFile
			mysqlParams.ReadMasterLogPos = stat.ReadMasterLogPos
			mysqlParams.RelayMasterLogFile = stat.RelayMasterLogFile
			mysqlParams.ExecMasterLogPos = stat.ExecMasterLogPos

			if !common.IsOnline() {
				log.WithFields(log.Fields{
					"mysqlParams": mysqlParams,
					"r":           r,
				}).Info("doMasterRecover info")
			}
			//选主
			if mysqlParams.MasterLogFile == "" {
				return
			}
			if stat.SlaveIORunning == yes {
				if stat.SlaveSQLRunning == yes {
					return
				}
			}
			mysqlParams.DB = db
			//是否同步完成
			if !(mysqlParams.MasterLogFile == mysqlParams.RelayMasterLogFile && mysqlParams.ReadMasterLogPos == mysqlParams.ExecMasterLogPos) {
				log.WithFields(log.Fields{
					"mysqlParams": mysqlParams,
				}).Info("doMasterRecover.mysqlParams 未同步完成")
				time.Sleep(time.Millisecond * 1000)
			}
			//从
			slaveResult.Store(fmt.Sprintf("%s%d", common.SlaveName, i), mysqlParams)
			//选主
			if masterInfo, ok := slaveResult.LoadOrStore(common.MasterName, mysqlParams); ok {
				if mysqlParams.RelayMasterLogFile > masterInfo.(*MysqlParams).RelayMasterLogFile {
					slaveResult.Store(common.MasterName, mysqlParams)
				}
				if mysqlParams.RelayMasterLogFile == masterInfo.(*MysqlParams).RelayMasterLogFile && mysqlParams.ExecMasterLogPos > masterInfo.(*MysqlParams).ExecMasterLogPos {
					slaveResult.Store(common.MasterName, mysqlParams)
				}
				return
			}
			//slaveResult.Store(common.MasterName, mysqlParams)
		}(i, info)
	}
	//}
	wg.Wait()
	// reset master
	cmd := []string{
		"stop slave",
		"reset slave all",
	}
	// 没有主节点
	selectedMaster, ok := slaveResult.Load(common.MasterName)
	if !ok {
		log.WithFields(log.Fields{
			"masterList": masterList,
		}).Error("doMasterRecover.slaveResult.Load select master faild!")
		cmdOne := cmd
		cmdOne = append(cmdOne, ResetMaster, StartSlave)
		slaveResult.Range(func(key, value any) bool {
			vv := value.(*MysqlParams)
			for _, c := range cmdOne {
				if err = vv.DB.Exec(c).Error; err != nil {
					log.WithFields(log.Fields{
						"vv": vv,
					}).Errorf("切主失败 %s error", ShowSlaveStatus)
					return false
				}
			}
			return true
		})
		return
	}
	masterMysqlParams := selectedMaster.(*MysqlParams)

	if err = masterMysqlParams.DB.Exec(ResetMaster).Error; err != nil {
		log.WithFields(log.Fields{
			"masterMysqlParams": masterMysqlParams,
			"err":               err,
		}).Errorf("select master %s", ResetMaster)
		return
	}
	//stop slave; reset slave all; change master; start slave; show slave status
	changeMasterSql := fmt.Sprintf("CHANGE MASTER TO MASTER_HOST='%s',"+
		"MASTER_USER='%s',MASTER_PASSWORD='%s',"+
		"MASTER_PORT=%d,MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=154;",
		masterMysqlParams.Host,
		common.AgentConfig.Sentinel.RdsReplMysqlUser,
		common.AgentConfig.Sentinel.RdsReplMysqlPwd,
		masterMysqlParams.Port,
	)

	cmd2 := []string{
		changeMasterSql,
		StartSlave,
	}
	slaveResult.Range(func(key, v any) bool {
		//停止所有从库
		vv := v.(*MysqlParams)
		for _, c := range cmd {
			if err = vv.DB.Exec(c).Error; err != nil {
				log.WithFields(log.Fields{
					"masterMysqlParams": masterMysqlParams,
					"err":               err,
				}).Errorf("select master %s", c)
				return false
			}
		}
		// 除新主外所有从，连接新主
		if masterMysqlParams.Host != vv.Host {
			for _, c2 := range cmd2 {
				if err = vv.DB.Exec(c2).Error; err != nil {
					log.WithFields(log.Fields{
						"masterMysqlParams": masterMysqlParams,
						"err":               err,
					}).Errorf("select master %s", c2)
					return false
				}
			}
			mysqlParams := &MysqlParams{}
			if err = vv.DB.Raw(ShowSlaveStatus).Scan(mysqlParams).Error; err != nil {
				log.WithFields(log.Fields{
					"vv": vv,
				}).Errorf("切主失败 %s error", ShowSlaveStatus)
				return false
			}
		}
		return true
	})
	log.WithFields(log.Fields{
		"masterList": masterList,
	}).Info("doMasterRecover success!")
}

func (r *Recover) getFailCluster() ([]Recover, error) {
	result := make([]Recover, 0)
	t := &mysql_sync.MySQLClusterTopology{
		ClusterAlias: r.ClusterAlias,
	}
	failOverList, err := t.List(common.Offset, common.Limit)
	if err != nil {
		return result, nil
	}
	masterHost := ""
	masterPort := uint32(0)
	for _, info := range failOverList {
		if info.Role == common.MasterName {
			masterPort = info.Port
			masterHost = info.Host
		}
	}
	for _, info := range failOverList {
		if r.Host == info.Host {
			continue
		}
		tmp := Recover{
			Host:         info.Host,
			Port:         info.Port,
			ClusterAlias: info.ClusterAlias,
			Role:         info.Role,
			MasterHost:   masterHost,
			MasterPort:   masterPort,
		}
		result = append(result, tmp)
	}

	return result, nil
}
